(ns ca.uoit.molly.db-molly
  (:use [clojure.contrib.sql :as sql]
        [clojure.contrib.str-utils])
  (:require [clojure.zip :as zip]))

(defn row-to-entity
  "Converts a row to an entity instance in accordance to the ent-def"
  [ent-def row]
  (let [attr-list (filter (complement #{:id}) (keys row))
        attr (apply array-map (flatten (for [k attr-list] [k (row k)])))
        text (str-join " " (for [k (ent-def :values)] (row k)))]
    {:id (row :id)
     :entity (ent-def :entity)
     :type "entity"
     :all text
     :attr attr}))
                    
(defn row-to-value
  "Converts a row to an entity value"
  [ent-def attr-name row]
  {:entity (ent-def :entity)
   :attr (name attr-name)
   :value (row :value)})

(defn select-all-sql
  [ent-def]
  (let [project-list (cons (ent-def :id) (vals (ent-def :attr)))]
    (format "SELECT %s FROM %s" (str-join "," project-list) (ent-def :from))))

(defn select-values-sql
  [ent-def attr-name]
  (let [select-clause (-> ent-def :attr attr-name)]
    (format "SELECT DISTINCT %s AS value FROM %s" select-clause (ent-def :from))))

(defn for-all-entities
  "Crawl through all the entity instances defined by ent-def
  and apply f to each"
  [db ent-def f & args]
  (sql/with-connection db
    (sql/with-query-results results [(select-all-sql ent-def)]
      (doseq [row results :let [entity (row-to-entity ent-def row)]]
        (apply f (cons entity args))))))

(defn for-all-values
  "Crawl through all the entity values defined by ent-def
  and apply f to each"
  [db ent-def f & args]
  (sql/with-connection db
    (doseq [attr-name (ent-def :values)]
      (sql/with-query-results results [(select-values-sql ent-def attr-name)]
        (doseq [row results :let [value (row-to-value ent-def attr-name row)]]
          (apply f (cons value args)))))))


; Entity groups
(defn- split-row [group-def row]
  (vec (for [h (:hierarchy group-def)]
    {:entity (h :entity)
     :id (-> :id h keyword row)
     :parameters (select-keys row (map keyword (h :parameters)))})))

(defn rowset-to-denorm-group
  [grp-def rowset]
  (for [row rowset]
    (split-row grp-def row)))

(defn select-group-sql
  ([grp-def]
    (select-group-sql grp-def nil))
  ([grp-def where-map]
  (let [sql (format "SELECT %s FROM %s %s" 
              (:project grp-def)
              (:from grp-def)
              (if (not (nil? where-map))
                (str "WHERE " 
                  (str-join " AND " 
                    (map-indexed 
                      #(format "%s=:%d" (name %2) (inc %1))
                      (keys where-map))))
                ""))]
    (println "select-group-sql: " sql)
    (vec (cons sql (vec (vals where-map)))))))

;(defn select-top-index-sql
;  [db grp-def]
;  (let [sql (format "SELECT DISTINCT %s FROM %s LIMIT 150" ; DEBUG CODE LIMIT 10
;              (:top-index grp-def)
;              (:from grp-def))]
;    ;(println "select-top-index-sql: " sql)
;    sql))

;(defn for-all-groups
;  "Crawl through all the entity groups defined by group-def
;  and apply f to each"
;  [db grp-def f]
;  (sql/with-connection db
;    (sql/with-query-results top-results [(select-top-index-sql db grp-def)]
;      (do
;        (println "There are " (count top-results) " distinct top indexes")
;        (doseq [top-index top-results]
;            (sql/with-query-results rowset (select-group-sql db grp-def top-index)
;              (f grp-def (rowset-to-denorm-group grp-def rowset))))))))

(defn for-all-groups
  "Crawl through all the entity groups defined by grp-def"
  [db grp-def f]
  (sql/with-connection db
    (sql/with-query-results results (select-group-sql grp-def)
      (doseq [[key rowset] (group-by #(select-keys % (:top-index grp-def)) results)]
        (f grp-def (rowset-to-denorm-group grp-def rowset))))))
